package com.gr.experimental.model.impl.distr;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ContextImpl implements Context {

	Map<String,Descriptor> descriptorMap = new HashMap<String, Descriptor>();

	/**
	 * The descriptor of the returned value;
	 */
	Descriptor returnDescriptor;
	boolean returnCalled;
	Context superContext;
	
	Map<String, Object> variables = new HashMap<String, Object>();
	
	public ContextImpl() {
	}
	
	public ContextImpl(Map<String,Descriptor> descriptorMap, Descriptor returnDescriptor) {
		this(null, descriptorMap, returnDescriptor);
	}

	public ContextImpl(Context superContext) {
		this(superContext, null, null);
	}	

	public ContextImpl(Context superContext, Map<String,Descriptor> descriptorMap, Descriptor returnDescriptor) {
		this.superContext = superContext;
		if (descriptorMap != null) {
			this.descriptorMap.putAll(descriptorMap);
		}
		if (returnDescriptor != null) {
			setReturnDescriptor(returnDescriptor);
		}
	}	
	

	@Override
	public Context getSuperContext() {
		return superContext;
	}
	
	@Override
	public Descriptor getDescriptor(String identifier) {
		Descriptor descriptor = descriptorMap.get(identifier);
		if (descriptor == null && superContext != null) {
			descriptor = superContext.getDescriptor(identifier);
		}
		return descriptor;
	}
	
	@Override
	public Map<String, Descriptor> getLocalDescriptorMap() {
		return descriptorMap;
	}
	
	@Override
	public Map<String,Descriptor> getAllDescriptorMap() {
		Map<String, Descriptor> map = new HashMap<String, Descriptor>();
		if (superContext != null) {
			map.putAll(superContext.getAllDescriptorMap());
		}
		map.putAll(descriptorMap);
		return Collections.unmodifiableMap(map);
	}

	@Override
	public void addDescriptor(Descriptor descriptor) {
		descriptorMap.put(descriptor.getSymbolName(), descriptor);
	}

	@Override
	public void setReturnDescriptor(Descriptor descriptor) {
		if (descriptor == null) {
			throw new IllegalArgumentException("return descriptor may not be null");
		}
		if (superContext != null && superContext.getReturnDescriptor() != null) {
			throw new IllegalArgumentException("super context already defines the return descriptor");
		}
		returnDescriptor = descriptor;
		descriptorMap.put(descriptor.getSymbolName(), descriptor);
	}

	@Override
	public boolean isReturnCalled() {
		return returnCalled;
	}

	@Override
	public void setReturnCalled(boolean returnCalled) {
		this.returnCalled = returnCalled;
	}

	@Override
	public Object getVariableValue(String identifier) {
		
		Descriptor descriptor = descriptorMap.get(identifier);
		if (descriptor == null) {
			if (superContext == null) {
				throw new IllegalArgumentException("Identifier: " + identifier + " unknown in the context");
			}
			return superContext.getVariableValue(identifier);
		}

		if (descriptor.isModifiable()) {
			return variables.get(identifier);
		} else {
			return descriptor.getConstantValue();
		}
	}

	@Override
	public void setVariableValue(String identifier, Object value) {
		Descriptor descriptor = getDescriptor(identifier);
		if (descriptor == null) {
			if (superContext == null) {
				throw new IllegalArgumentException("Identifier: " + identifier + " unknown in the context");
			}
			superContext.setVariableValue(identifier, value);
		}
		
		if (!descriptor.isModifiable()) {
			throw new IllegalArgumentException(identifier + " not modifiable");
		}
		if (!descriptor.getType().isAssignableFrom(value.getClass())) {
			throw new IllegalArgumentException("Incompatible types: " + descriptor.getType() + " and " + value.getClass());
		}
		this.variables.put(identifier, value);
	}

	@Override
	public Descriptor getReturnDescriptor() {
		if (returnDescriptor == null && superContext != null) {
			return superContext.getReturnDescriptor();
		}
		return returnDescriptor;
	}


}
